﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Reflection;

namespace SEAM.Data
{
    #region DataTable Extenders

    public static class DataTableExtenders
    {
        public static T First<T>(this DataTable dtResults)
            where T : new()
        {
            return ElementAt<T>(dtResults, 0);
        }

        public static T ElementAt<T>(this DataTable dtResults, int index)
            where T : new()
        {
            if (dtResults == null || dtResults.Rows == null || dtResults.Rows.Count <= index)
                return default(T);
            else
                return dtResults.Rows[index].Cast<T>();
        }

        public static List<T> Cast<T>(this DataTable dtResults)
            where T : new()
        {
            List<T> retVal = new List<T>();
            if (dtResults != null && dtResults.Rows != null)
            {
                for (int i = 0; i < dtResults.Rows.Count; i++)
                    retVal.Add(dtResults.Rows[i].Cast<T>());
            }
            return retVal;
        }
    }

    #endregion

    #region DataReader Extenders

    public static class DbDataReaderExtenders
    {
        public static bool ContainsField(this DbDataReader drResults, string fieldName)
        {
            try
            {
                object val = drResults[fieldName];
                return true;
            }
            catch (IndexOutOfRangeException)
            {
                return false;
            }
        }

        public static IDictionary<string, object> ToDictionary(this DbDataReader drResults)
        {
            if (drResults == null)
                return null;
            IDictionary<string, object> retVal = new Dictionary<string, object>();
            for (int i = 0; i < drResults.FieldCount; i++)
                retVal.Add(drResults.GetName(i), drResults[i] == DBNull.Value ? null : drResults[i]);
            return retVal;
        }

        public static T ToType<T>(this DbDataReader drResults)
            where T : new()
        {
            if (drResults == null)
                return default(T);
            else if (typeof(T) is IBindable)
            {
                T retVal = new T();
                ((IBindable)retVal).Bind(drResults.ToDictionary());
                return retVal;
            }
            else
            {

                if (drResults.FieldCount == 1 && typeof(T).IsSupportedType())
                    return (T)ExtenderHelper.GetObject(drResults[0], typeof(T));

                T retVal = new T();
                object boxedRetVal = retVal;

                MemberInfo[] bindMembers = retVal.GetType().FindMembers(MemberTypes.Field | MemberTypes.Property, BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, new MemberFilter(ExtenderHelper.FillObject), null);
                foreach (MemberInfo member in bindMembers)
                {
                    object[] attrs = member.GetCustomAttributes(typeof(BindAttribute), true);
                    if (attrs != null && attrs.Length > 0)
                    {
                        foreach (BindAttribute attr in attrs)
                        {
                            if (drResults.ContainsField(attr.Name))
                            {
                                object setVal = (drResults[attr.Name] == DBNull.Value ? null : drResults[attr.Name]);

                                if (member.MemberType == MemberTypes.Field)
                                    ((FieldInfo)member).SetValue(boxedRetVal, setVal);
                                else if (member.MemberType == MemberTypes.Property && ((PropertyInfo)member).CanWrite)
                                    ((PropertyInfo)member).SetValue(boxedRetVal, setVal, null);
                            }
                        }
                    }
                }

                return (T)boxedRetVal;
            }
        }
    }

    #endregion

    #region DataRow Extenders

    public static class DataRowExtenders
    {
        public static IDictionary<string, object> ToDictionary(this DataRow drResults)
        {
            if (drResults == null)
                return null;
            IDictionary<string, object> retVal = new Dictionary<string, object>();
            foreach (DataColumn aCol in drResults.Table.Columns)
                retVal.Add(aCol.ColumnName, drResults[aCol] == DBNull.Value ? null : drResults[aCol]);
            return retVal;
        }

        public static T Cast<T>(this DataRow drResults)
            where T : new()
        {
            if (drResults == null)
                return default(T);
            else if (typeof(T) is IBindable)
            {
                T retVal = new T();
                ((IBindable)retVal).Bind(drResults.ToDictionary());
                return retVal;
            }
            else
            {
                if (drResults.Table.Columns.Count == 1 && typeof(T).IsSupportedType())
                    return (T)ExtenderHelper.GetObject(drResults[0], typeof(T));

                T retVal = new T();
                object boxedRetVal = retVal;

                MemberInfo[] bindMembers = retVal.GetType().FindMembers(MemberTypes.Field | MemberTypes.Property, BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, new MemberFilter(ExtenderHelper.FillObject), null);
                foreach (MemberInfo member in bindMembers)
                {
                    object[] attrs = member.GetCustomAttributes(typeof(BindAttribute), true);
                    if (attrs != null && attrs.Length > 0)
                    {
                        foreach (BindAttribute attr in attrs)
                        {
                            if (drResults.Table.Columns.Contains(attr.Name))
                            {
                                object setVal = (drResults[attr.Name] == DBNull.Value ? null : drResults[attr.Name]);

                                if (member.MemberType == MemberTypes.Field)
                                    ((FieldInfo)member).SetValue(boxedRetVal, setVal);
                                else if (member.MemberType == MemberTypes.Property && ((PropertyInfo)member).CanWrite)
                                    ((PropertyInfo)member).SetValue(boxedRetVal, setVal, null);
                            }
                        }
                    }
                }

                return (T)boxedRetVal;
            }
        }

        #region Helper Methods
        #endregion
    }

    #endregion

    #region Type Extenders

    public static class TypeExtenders
    {
        public static bool IsNullable(this Type objType)
        {
            if (objType.IsGenericType && objType.GetGenericTypeDefinition() == typeof(Nullable<>) && objType.GetGenericArguments().Length == 1)
                return true;
            return false;
        }

        public static Type GetNullableBaseType(this Type objType)
        {
            if (objType.IsNullable())
                return objType.GetGenericArguments()[0];
            return objType;
        }

        public static bool IsSupportedType(this Type objType)
        {
            Type objBaseType = objType.GetNullableBaseType();

            return (
                objBaseType == typeof(bool) || objBaseType == typeof(byte) ||
                objBaseType == typeof(short) || objBaseType == typeof(int) ||
                objBaseType == typeof(long) || objBaseType == typeof(float) ||
                objBaseType == typeof(DateTime) || objBaseType == typeof(string)
            );
        }

    }

    #endregion

    #region DataAccessParam Extenders

    public static class DataAccessParamExtenders
    {
        public static DataAccessParam GetParameter(this bool? value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this bool? value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Boolean)
            {
                ParamDirection = paramDirection,
                Value = (value.HasValue ? value : null)
            };
        }
        public static DataAccessParam GetParameter(this bool value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this bool value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Boolean)
            {
                ParamDirection = paramDirection,
                Value = value
            };
        }

        public static DataAccessParam GetParameter(this byte? value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this byte? value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Byte)
            {
                ParamDirection = paramDirection,
                Value = (value.HasValue ? value : null)
            };
        }
        public static DataAccessParam GetParameter(this byte value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this byte value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Byte)
            {
                ParamDirection = paramDirection,
                Value = value
            };
        }

        public static DataAccessParam GetParameter(this short? value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this short? value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Short)
            {
                ParamDirection = paramDirection,
                Value = (value.HasValue ? value : null)
            };
        }
        public static DataAccessParam GetParameter(this short value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this short value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Short)
            {
                ParamDirection = paramDirection,
                Value = value
            };
        }

        public static DataAccessParam GetParameter(this Int32 value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this Int32 value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Integer)
            {
                ParamDirection = paramDirection,
                Value = value
            };
        }
        public static DataAccessParam GetParameter(this int? value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this int? value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Integer)
            {
                ParamDirection = paramDirection,
                Value = (value.HasValue ? value : null)
            };
        }
        public static DataAccessParam GetParameter(this long? value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this long? value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Long)
            {
                ParamDirection = paramDirection,
                Value = (value.HasValue ? value : null)
            };
        }
        public static DataAccessParam GetParameter(this long value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this long value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Long)
            {
                ParamDirection = paramDirection,
                Value = value
            };
        }

        public static DataAccessParam GetParameter(this float? value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this float? value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Float)
            {
                ParamDirection = paramDirection,
                Value = (value.HasValue ? value : null)
            };
        }
        public static DataAccessParam GetParameter(this float value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this float value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Float)
            {
                ParamDirection = paramDirection,
                Value = value
            };
        }

        public static DataAccessParam GetParameter(this decimal? value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this decimal? value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Float)
            {
                ParamDirection = paramDirection,
                Value = (value.HasValue ? value : null)
            };
        }
        public static DataAccessParam GetParameter(this decimal value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this decimal value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.Float)
            {
                ParamDirection = paramDirection,
                Value = value
            };
        }

        public static DataAccessParam GetParameter(this DateTime? value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this DateTime? value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.DateTime)
            {
                ParamDirection = paramDirection,
                Value = (value.HasValue ? value : null)
            };
        }
        public static DataAccessParam GetParameter(this DateTime value, string paramName)
        {
            return value.GetParameter(paramName, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this DateTime value, string paramName, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.DateTime)
            {
                ParamDirection = paramDirection,
                Value = value
            };
        }

        public static DataAccessParam GetParameter(this string value, string paramName, int paramSize)
        {
            return value.GetParameter(paramName, paramSize, ParameterDirection.Input);
        }
        public static DataAccessParam GetParameter(this string value, string paramName, int paramSize, ParameterDirection paramDirection)
        {
            return new DataAccessParam(paramName, ParamType.String, paramSize)
            {
                ParamDirection = paramDirection,
                Value = value
            };
        }
    }

    #endregion

    #region ExtenderHelper Class

    internal class ExtenderHelper
    {
        public static bool FillObject(MemberInfo objMemberInfo, object objInfo)
        {
            if (objMemberInfo == null)
                return false;
            else
                return (objMemberInfo.GetCustomAttributes(typeof(BindAttribute), true).Length > 0);
        }

        public static object GetObject(object Input, Type objType)
        {
            if (Input == DBNull.Value)
                return null;
            return Convert.ChangeType(Input, objType.GetNullableBaseType());

            /*
            if (objType.IsSupportedType())
            {
                //bool isNullable = objType.IsNullable();
                Type objBaseType = objType.GetNullableBaseType();

                if (objBaseType == typeof(byte))
                    return DBUtility.GetByte(Input);
                else if (objBaseType == typeof(int))
                    return DBUtility.GetInteger(Input);
                else if (objBaseType == typeof(short))
                    return DBUtility.GetShort(Input);
                else if (objBaseType == typeof(long))
                    return DBUtility.GetLong(Input);
                else if (objBaseType == typeof(string))
                    return DBUtility.GetString(Input);
                else if (objBaseType == typeof(bool))
                    return DBUtility.GetBool(Input);
                else if (objBaseType == typeof(DateTime))
                    return DBUtility.GetDateTime(Input);
                else if (objBaseType == typeof(float))
                    return DBUtility.GetFloat(Input);
            }
            return null;
            */
        }

    }

    #endregion
}
